/* API PROPOSAL for RMI-C PROJECT (V1). */

#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <rpc/xdr.h>
#include <pthread.h>
#include <assert.h>
#include "rmi.h"

/* ************************************************************ */

#define MAXBUF 400
#define MAXMETHODS 100
#define MAXOBJECTS 100
#define METHOD_QUIT 0
#define MAX_ARRAY_SIZE 256

#define BIND_REQUEST 0
#define LOOKUP_REQUEST 1
/* ************************************************************ */
/*                           XDR TYPES                          */
/* ************************************************************ */

bool_t xdr_xarray(XDR * xdrs, xarray * xarray)
{
  /* force XDR to allocate memory while decoding */
  
  if((xdrs->x_op == XDR_DECODE) && (xarray->array != NULL)) {
    free(xarray->array); /* ??? */
    xarray->array = NULL;
  }

  return xdr_array(xdrs,
		   (char**)(&(xarray->array)),
		   &(xarray->size),
		   MAX_ARRAY_SIZE,
		   sizeof(int),
		   (xdrproc_t)xdr_int);
}

/* ************************************************************ */
/*                          CLIENT API                          */
/* ************************************************************ */

struct CLIENT_t {
  char host[255];
  int port;
  int fd;
  unsigned long objecttype;
  unsigned long objectkey;
};

/* ************************************************************ */

CLIENT * rmi_client_create(char *host, 
			   int port,
			   unsigned long objecttype,
			   unsigned long objectkey)
{
  /* connect server */
  fprintf(stderr, "Connecting %s:%d...\n", host, port);
  CLIENT * client = malloc(sizeof(CLIENT));
  strcpy(client->host, host);
  client->port = port;
  client->objecttype = objecttype;
  client->objectkey = objectkey;
  client->fd = socket(AF_INET,SOCK_STREAM,0);
  assert(client->fd > 0);
  struct hostent * server = gethostbyname(host);
  assert(server != NULL);
  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);                          
  addr.sin_addr.s_addr = *(unsigned long *)server->h_addr_list[0];   
  int err = connect(client->fd,(struct sockaddr *)&addr, sizeof(addr));
  assert(err == 0);
  
  /* write object parameters */
  write(client->fd, &client->objecttype, sizeof(unsigned long));  
  write(client->fd, &client->objectkey, sizeof(unsigned long));  
  
  return client;
}

/* ************************************************************ */

int rmi_client_call(CLIENT *client, 
		    unsigned long methodnum,
		    xdrproc_t inproc, 
		    void *in,
		    xdrproc_t outproc, 
		    void *out)
{
  /* write request parameters */
  write(client->fd, &methodnum, sizeof(unsigned long));  
  
  /* write input arguments */
  XDR xdrin;
  char bufin[MAXBUF];
  xdrmem_create(&xdrin, bufin, MAXBUF, XDR_ENCODE);
  bool resin = inproc(&xdrin, in);
  write(client->fd, bufin, MAXBUF);  
  xdr_destroy(&xdrin);
  assert(resin);
  
  /* read output arguments */
  XDR xdrout; 
  char bufout[MAXBUF];
  read(client->fd, bufout, MAXBUF);  
  xdrmem_create(&xdrout, bufout, MAXBUF, XDR_DECODE);
  bool resout = outproc(&xdrout, out);
  xdr_destroy(&xdrout);
  assert(resout);

}

/* ************************************************************ */

void rmi_client_destroy(CLIENT *client)
{
  int methodnum = METHOD_QUIT;
  write(client->fd, &methodnum, sizeof(unsigned long)); 
  close(client->fd);
}

/* ************************************************************ */

unsigned long rmi_client_lookup(char object_name[MAXBUF], int port, char host[256]){

  unsigned long object_adress;
  
/*--Connection to naming server--*/
  fprintf(stderr, "Connecting to naming server %s:%d...\n", host, port);
  int fd = socket(AF_INET,SOCK_STREAM,0);
  int lookup_request = LOOKUP_REQUEST;
  assert(fd > 0);
  struct hostent * server = gethostbyname(host);
  assert(server != NULL);
  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr.s_addr = *(unsigned long *)server->h_addr_list[0];
  int err = connect(fd,(struct sockaddr *)&addr, sizeof(addr));
  assert(err == 0);
  
  /*--Sending bind request--*/
  err = write(fd,&lookup_request, sizeof(int));
  assert(err > 0);

  /*--Sending the object name--*/
  err = write(fd,object_name,MAXBUF*sizeof(char));
  assert(err > 0);
  
  /*---Receiving the object adress-*/
  err = read(fd,&object_adress,sizeof(unsigned long));
  assert(err > 0);

  close(fd);
  return object_adress;
}

/* ************************************************************ */
/*                          SERVER API                          */
/* ************************************************************ */

typedef struct METHOD_t {
  unsigned long objecttype;
  unsigned long objectkey;
  unsigned long methodnum;
  xdrproc_t inproc;
  xdrproc_t outproc;
  method_t method;
} METHOD;

typedef struct OBJECT_t {
  unsigned long objecttype;
  unsigned long objectkey;
  void * this;
} OBJECT;

typedef struct REQUEST_t { 
  int client;
  unsigned long objecttype;
  unsigned long objectkey;
  unsigned long methodnum;
  char in[MAXBUF];  /* xdr raw buffer */
  char out[MAXBUF]; /* xdr raw buffer */
} REQUEST;

struct SERVER_t { 
  int port;  
  int fd;  
  int nb_methods;
  int nb_objects;
  METHOD methods[MAXMETHODS];
  OBJECT objects[MAXOBJECTS];
};

typedef struct CONNECTION_t { 
  SERVER * server;
  pthread_t tid;
  int client; /* socket */
  unsigned long objecttype;
  unsigned long objectkey;
} CONNECTION;

/* ************************************************************ */

int rmi_server_activate_object(SERVER * server,  
			       unsigned long objecttype,
			       unsigned long objectkey,
			       void * this)
{
  int i = server->nb_objects;
  server->nb_objects++;  
  server->objects[i].objecttype = objecttype;
  server->objects[i].objectkey = objectkey; 
  server->objects[i].this = this; 
}

/* ************************************************************ */

int rmi_server_find_object(SERVER * server, 
			   unsigned long objecttype,
			   unsigned long objectkey)
{
  for(int i = 0 ; i < server->nb_objects ; i++)
    if(server->objects[i].objecttype == objecttype && 
       server->objects[i].objectkey == objectkey) return i;
  return -1; /* not found */ 
}

/* ************************************************************ */

int rmi_server_register_method(SERVER * server, 
			       unsigned long objecttype,
			       unsigned long methodnum,
			       xdrproc_t inproc,
			       xdrproc_t outproc,
			       method_t method)
{
  int i = server->nb_methods;
  server->nb_methods++;  
  server->methods[i].objecttype = objecttype;
  server->methods[i].methodnum = methodnum;
  server->methods[i].inproc = inproc;
  server->methods[i].outproc = outproc;
  server->methods[i].method = method;
  return i; 
}

/* ************************************************************ */

int rmi_server_unregister_method(SERVER * server, 
				 unsigned long objecttype,
				 unsigned long methodnum)
{
  
}

/* ************************************************************ */

int rmi_server_deactivate_object(SERVER * server,  
				 unsigned long objecttype,
				 unsigned long objectkey)
{
  
}

/* ************************************************************ */

int rmi_server_find_method(SERVER * server, 
			   unsigned long objecttype,
			   unsigned long methodnum)
{
  for(int i = 0 ; i < server->nb_methods ; i++)
    if(server->methods[i].objecttype == objecttype && 
       server->methods[i].methodnum == methodnum) return i;
  return -1; /* not found */ 
}

/* ************************************************************ */

SERVER * rmi_server_create(int port)
{
  SERVER * server = malloc(sizeof(SERVER));
  server->nb_methods = 0;			
  server->nb_objects = 0;			
  server->port = port;
  server->fd = socket(AF_INET, SOCK_STREAM, 0);
  assert(server->fd > 0);
  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = htonl(INADDR_ANY);      
  addr.sin_port = htons(port);            
  int err = bind(server->fd,(struct sockaddr*)&addr, sizeof(addr));
  assert(err >= 0);
  listen(server->fd, 4);
  fprintf(stderr, "server listen on port %d\n", port);  
  return server;
}

/* ************************************************************ */

void dispatch(SERVER * server, 
	      REQUEST * req)
{
  /* found method */
  int k = rmi_server_find_method(server, req->objecttype, req->methodnum);
  
  if(k < 0) {
    fprintf(stderr,"method not register (methodnum %ld)\n", req->methodnum);
    close(req->client);
    exit(EXIT_FAILURE);
  }
  
  /* found object  */
  int o = rmi_server_find_object(server, req->objecttype, req->objectkey);
  
  if(o < 0) {
    fprintf(stderr,"object not activated (objectkey %ld)\n", req->objectkey);
    close(req->client);
    exit(EXIT_FAILURE);
  }
  
  void * this = server->objects[o].this;
  
  /* process method */
  method_t method = server->methods[k].method;
  xdrproc_t inproc  = server->methods[k].inproc;
  xdrproc_t outproc = server->methods[k].outproc;
  char in[MAXBUF];
  char out[MAXBUF];
  
  /* input arg */
  XDR xdrin; 
  xdrmem_create(&xdrin, req->in, MAXBUF, XDR_DECODE);
  bool resin = inproc(&xdrin, in); 
  xdr_destroy(&xdrin);
  assert(resin);
  
  /* call method */
  method(this, in, out);
  
  /* output arg */
  XDR xdrout; 
  xdrmem_create(&xdrout, req->out, MAXBUF, XDR_ENCODE);
  bool resout = outproc(&xdrout, out); 
  xdr_destroy(&xdrout);
  assert(resout);  
}

/* ************************************************************ */

void* work(void * arg)
{
  CONNECTION * connection = (CONNECTION *)arg;
  SERVER * server = connection->server;
  int client = connection->client;
  unsigned long objecttype = connection->objecttype;
  unsigned long objectkey = connection->objectkey;
  
  while(1) {
    
    /* read request parameters */
    unsigned long methodnum;
    read(client, &methodnum, sizeof(unsigned long));
    fprintf(stderr,"call method %ld of object %ld (type %ld)\n", methodnum, objectkey, objecttype);      
    if(methodnum == METHOD_QUIT) break;
    
    /* request */
    REQUEST req;
    req.client = client;
    req.objecttype = objecttype;
    req.objectkey = objectkey;
    req.methodnum = methodnum;
    
    /* read input arguments */
    read(client, req.in, MAXBUF); 
    
    /* dispatch method call */
    dispatch(server, &req);
    
    /* write output arguments */
    write(client, req.out, MAXBUF); 
  }
  
  /* close & free connection */
  close(client);
  free(connection);
  fprintf(stderr,"bye bye of object %ld (type %ld)\n", objectkey, objecttype);  
  
  return NULL;
}


/* ************************************************************ */

void rmi_server_run(SERVER * server)
{
  while(1) {
    CONNECTION * connection = malloc(sizeof(CONNECTION));
    connection->server = server;
    connection->client = accept(server->fd, NULL, NULL);
    read(connection->client, &connection->objecttype, sizeof(unsigned long));
    read(connection->client, &connection->objectkey, sizeof(unsigned long));
    fprintf(stderr,"Connection established for object %ld (of type %ld)\n",
	    connection->objectkey, connection->objecttype);
    pthread_create(&connection->tid, NULL, work, connection);
  }
}

/* ************************************************************ */

void rmi_serveur_bind(unsigned long object_adress, char object_name[MAXBUF], int port, char host[256]){
  /*--Connection to naming server--*/
  fprintf(stderr, "Connecting to naming server %s:%d...\n", host, port);
  int fd = socket(AF_INET,SOCK_STREAM,0);
  int bind_request = BIND_REQUEST;
  assert(fd > 0);
  struct hostent * server = gethostbyname(host);
  assert(server != NULL);
  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr.s_addr = *(unsigned long *)server->h_addr_list[0];
  int err = connect(fd,(struct sockaddr *)&addr, sizeof(addr));
  assert(err == 0);
  
  /*--Sending bind request--*/
  err = write(fd,&bind_request, sizeof(int));
  assert(err > 0);

  /*--Sending the object name--*/
  err = write(fd,object_name,MAXBUF*sizeof(char));
  assert(err > 0);
  
  /*---Sending the object adress-*/
  err = write(fd,&object_adress,sizeof(unsigned long));
  assert(err > 0);
  
  close(fd);
}
